package com.sstir.doctor.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.sstir.doctor.common.DocType;
import com.sstir.doctor.common.EnumUtil;
import com.sstir.doctor.es.doc.DoctorDoc;
import com.sstir.doctor.pojo.dto.DoctorDocDTO;
import com.sstir.doctor.pojo.query.DocSearchQuery;
import com.sstir.doctor.pojo.query.GenericQueryPage;
import com.sstir.doctor.service.EsSearchService;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * <p>
 * ES搜索服务实现类
 * </p>
 *
 * @author: fli
 * @email: fli@sstir.cn
 * @date: 2021/1/15 12:39
 */
@Service
public class EsSearchServiceImpl implements EsSearchService {

    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

    @Override
    @SuppressWarnings("unchecked")
    public IPage<DoctorDocDTO> searchDocs(GenericQueryPage<DocSearchQuery> queryPage) {

        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

        // 关键词
        final String keyword = queryPage.getParams().getKeyword();
        // 构建查询
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        if (StringUtils.isNotBlank(keyword)) {
            boolQuery
                    .should(QueryBuilders.matchQuery("value",keyword).operator(Operator.AND))
                    .should(QueryBuilders.wildcardQuery("key.keyword","*".concat(keyword).concat("*")));
                   // .should(QueryBuilders.matchQuery("value.pinyin",keyword.replaceAll("\\s*", "")));
            boolQuery.minimumShouldMatch(1);
        }


        final String docType = queryPage.getParams().getDocType();
        BoolQueryBuilder filterQuery = QueryBuilders.boolQuery();
        if(StringUtils.isNotBlank(docType)) {
            filterQuery.must(QueryBuilders.termQuery("type",docType));
        }

        boolQuery.filter(filterQuery);

        // 指定查询
        queryBuilder.withQuery(boolQuery);

        // 结果集过滤，显示指定字段
        queryBuilder.withSourceFilter(new FetchSourceFilter(null,new String[]{"key"}));


        // 分页
        queryBuilder.withPageable(PageRequest.of((int)queryPage.getCurrent() -1,(int)queryPage.getSize()));

        // 高亮
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder
                .field(
                        new HighlightBuilder
                                .Field("value")
                                .preTags("<font color='#ff6600'>")
                                .postTags("</font>")
                                .numOfFragments(1)
                                .fragmentSize(30)
                );
        queryBuilder.withHighlightBuilder(highlightBuilder);


        // 转化的对象
        List<DoctorDocDTO> dtoList = new ArrayList<>();
        // 执行搜索
        org.springframework.data.domain.Page<DoctorDoc> doctorDocPage = elasticsearchTemplate.queryForPage(queryBuilder.build(), DoctorDoc.class, new SearchResultMapper() {
            @Override
            public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
                List<DoctorDoc> doctorDocList = new ArrayList<>();
                for (SearchHit searchHit : searchResponse.getHits()) {
                    doctorDocList.add((DoctorDoc)mapSearchHit(searchHit,aClass));
                }
                return new AggregatedPageImpl(doctorDocList, pageable, searchResponse.getHits().getTotalHits(), searchResponse.getAggregations());
            }

            @Override
            public <T> T mapSearchHit(SearchHit searchHit, Class<T> aClass) {
                Map<String, Object> source = searchHit.getSourceAsMap();
                DoctorDoc doctorDoc = BeanUtil.mapToBean(source, DoctorDoc.class, true, null);
                // 处理高亮
                StringBuilder sb = new StringBuilder();
                Map<String, HighlightField> highlightFields = searchHit.getHighlightFields();
                if(highlightFields != null) {
                    for (Map.Entry<String, HighlightField> entry : highlightFields.entrySet()) {
                        HighlightField value = entry.getValue();
                        Text[] fragments = value.getFragments();
                        if(fragments != null) {
                            for (Text text : fragments) {
                                sb.append(text.toString());
                            }
                        }
                    }
                }
                String highlightContent = sb.toString();
                // 处理dto
                DoctorDocDTO docDTO = new DoctorDocDTO();
                docDTO.setId(doctorDoc.getId());
                docDTO.setDocType((DocType)EnumUtil.fromValue(DocType.class,doctorDoc.getType()));
                String value = doctorDoc.getValue();
                docDTO.setContent(StringUtils.isBlank(highlightContent) ? value : highlightContent);
                Map<String,Object> sourceMap = new HashMap<>(16);
                String[] kvs = StringUtils.split(value,"|");
                if(kvs != null) {
                    for (String kv : kvs) {
                        String[] attrs = StringUtils.split(kv, ":");

                        sourceMap.put(attrs[0],attrs.length > 1 ? attrs[1] : "");
                    }
                }
                docDTO.setSourceMap(sourceMap);
                dtoList.add(docDTO);
                return (T)doctorDoc;
            }
        });
        IPage<DoctorDocDTO> page = new Page<>();
        page.setTotal(doctorDocPage.getTotalElements());
        page.setSize(doctorDocPage.getSize());
        page.setCurrent(doctorDocPage.getNumber() + 1);
        page.setRecords(dtoList);
        return page;
    }
}
